﻿//
// Copyright (c) 2011 David Díaz & Fran Pedreira
//
// This file is part of PlanSim.
//
// PlanSim is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PlanSim is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with PlanSim. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using MyTimer = System.Timers.Timer;

namespace PlanSim.Core
{
    public class Planificador : IPlanificador
    {
		private class ComparadorDePrioridades : IComparer<ICola>
		{
			public int Compare(ICola x, ICola y)
			{
				if (x.Prioridad > y.Prioridad)
					return 1;
				if (x.Prioridad < y.Prioridad)
					return -1;
				else
					return 0;
			}
		}

		public enum EstadoPlanificador
		{
			Simulando,
			Pausa,
			Detenido
		}
		
		/// <summary>
		/// Obtiene el estado del planificador.
		/// </summary>
		public EstadoPlanificador Estado
		{
			get { return estado; }
			//set { estado = value; }
		}
		private EstadoPlanificador estado = EstadoPlanificador.Detenido;

		/// <summary>
		/// Evento que se lanza al inicio de la simulación.
		/// </summary>
		public event EventHandler ComienzaSimulacion;

		/// <summary>
		/// Evento que se lanza a la finalización de la simulación.
		/// </summary>
		public event EventHandler FinalizaSimulacion;

		/// <summary>
		/// Evento que se lanza al inicio de cada ciclo de simulación.
		/// </summary>
		public event EventHandler ComienzaCiclo;

		/// <summary>
		/// Evento que se lanza a la finalización de cada ciclo de simulación.
		/// </summary>
		public event EventHandler FinalizaCiclo;

		/// <summary>
		/// Lista de colas de procesos preparados para pasar a ejecución.
		/// </summary>
        //List<ICola> preparados = new List<ICola>();
        public PriorityQueue<ICola> ColasPreparados
        {
            get { return colasPreparados; }
            // set { preparados = value; }
        }
        PriorityQueue<ICola> colasPreparados = new PriorityQueue<ICola>(new ComparadorDePrioridades());

		/// <summary>
		/// Lista de colas de procesos en espera de entrada/salida.
		/// </summary>
		public List<ICola> ColasEntradaSalida
		{
			get { return colasEntradaSalida; }
			//set { colasEntradaSalida = value; }
		}
		List<ICola> colasEntradaSalida = new List<ICola>();

        /// <summary>
        /// Obtiene una referencia a la lista de colas de Entrada/Salida.
        /// </summary>
		public List<ICola> EntradaSalida
		{
			get { return colasEntradaSalida; }
			set { colasEntradaSalida = value; }
		}

        /// <summary>
        /// Obtiene una referencia al proceso que está actualmente en ejecución.
        /// </summary>
        public IProceso ProcesoEnCPU { get { return CPU; } }
        IProceso CPU = null; // PENDIENTE: convirtiendo CPU en CPU[] podemos tener múltiples cores.

        /// <summary>
        /// Obtiene una referencia al proceso que está en entrada/salida.
        /// </summary>
        public IProceso ProcesoEnEntradaSalida
        {
            get { return dispositivoES; }
            set { dispositivoES = value; }
        }
		IProceso dispositivoES = null; // PENDIENTE: convirtiendo DispositivoES en DispositivoES [] podemos tener múltiples dispositivos ES.
		
		/// <summary>
		/// Obtiene el número de ciclos que lleva ejecutándose el último proceso asignado a la CPU.
		/// </summary>
		public int CiclosEnCPU { get { return ciclosEnCPU; } }
		int ciclosEnCPU = 0; // PENDIENTE: para multi-core esto debería ser ciclosEnCPU[]

		/// <summary>
		/// Obtiene el número de ciclos que lleva el proceso en E/S.
		/// </summary>
		public int CiclosEnES { get { return ciclosEnES; } }
		int ciclosEnES = 0; // PENDIENTE: para multi dispositivo E/S esto debería ser ciclosEnES[]

		/// <summary>
		/// Ciclo actual de ejecución.
		/// </summary>
		public int CicloActual { get { return cicloActual; } }
		int cicloActual = 0;

		/// <summary>
		/// Timer que se encarga de ir avanzando la simulación.
		/// </summary>
		private MyTimer timer = new MyTimer();

		/// <summary>
		/// Este mutex evita generar un ciclo de simulación mientras el anterior está en ejecución.
		/// Esto ocurre para frecuencias de simulación altas.
		/// </summary>
		private Mutex mutex = new Mutex();

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="frecuencia">Frecuencia de ejecución de la simulación. En Hz (ciclos/segundo).</param>
        public Planificador(Configuracion configuracion)
        {
			cicloActual = 0;

			// Configura la frecuencia del reloj.
			this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

			// Solo dispondremos de un dispositivo ES con una cola FCFS
			colasEntradaSalida.Add(new ColaFCFS("ES", 0));

			// Crea la MLQ de procesos preparados.
			foreach (Configuracion.Cola cola in configuracion.Colas)
				colasPreparados.Push(FactoriaCola.CrearCola(cola));
        }

		/// <summary>
		/// Evento de reloj que hace avanzar la simulación.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void timer_Elapsed(object sender, ElapsedEventArgs e)
		{
            // El mutex evita generar un ciclo mientras el anterior está en ejecución.
            // Esto ocurre para frecuencias de simulación altas.
            mutex.WaitOne();

			if ( estado == EstadoPlanificador.Simulando)
				Ciclo();

            mutex.ReleaseMutex();
		}

		/// <summary>
		/// Inicia la simulación.
		/// </summary>
		public void IniciarSimulacion(int frecuencia)
		{
			int interval = Math.Max(1000 / frecuencia, 1);
			this.timer.Interval = interval;

			switch (estado)
			{
				case EstadoPlanificador.Detenido:
					cicloActual = 0;

					// Lanza el evento de inicio de simulación
					OnComienzaSimulacion();

					estado = EstadoPlanificador.Simulando;
					timer.Enabled = true;
					break;

				case EstadoPlanificador.Pausa:
					estado = EstadoPlanificador.Simulando;
					timer.Enabled = true;
					break;

				default:
					break;
			}
		}

		public void PausarSimulacion()
		{
			estado = EstadoPlanificador.Pausa;
			timer.Enabled = false;
		}

		/// <summary>
		/// Detiene la simulación.
		/// </summary>
		public void DetenerSimulacion()
		{
			estado = EstadoPlanificador.Detenido;
			timer.Enabled = false;

			// Lanza el evento de fin de simulación.
			OnFinalizaSimulacion();
		}

		/// <summary>
		/// Añade un nuevo proceso a la simulación.
		/// </summary>
		/// <param name="nuevoProceso">Nuevo proceso a añadir.</param>
        public void InsertarProceso(IProceso proceso, string colaInicial)
        {
			ICola cola = null;

			for (int c = 0; c < colasPreparados.Count; ++c)
				if (colasPreparados[c].Nombre.ToLower() == colaInicial.ToLower())
				{
					cola = colasPreparados[c];
					break;
				}

			if (cola != null)
			{
				// Asocia el proceso a una cola inicial.
				proceso.Cola = cola;

				// Encolamos el proceso en la cola de "paraparos" para entrar a la CPU.
				EncolarEnPreparados(proceso);
			}
			else
				throw new ArgumentException("No se encontró la cola " + colaInicial);
        }

		private void EncolarEnPreparados(IProceso proceso)
		{
			// Se encola el proceso en su cola asociada.
			proceso.Cola.Encolar(proceso);
		}

		private IProceso DesencolarDePreparados()
		{
			// Recorre las colas de "preparados" en orden de prioridad
			for (int c = 0; c < colasPreparados.Count; ++c)
				if (colasPreparados[c].Elementos() > 0)
					return colasPreparados[c].Desencolar();

			return null;
		}

		/// <summary>
		/// Obtiene el estado de un proceso dentro de la planificación.
		/// </summary>
		/// <param name="proceso">Proceso por el que se pregunta.</param>
		/// <returns>Estado en el que se encuentra el proceso.</returns>
		public EstadoProceso.PosicionProceso PosicionProceso(IProceso proceso)
		{
			EstadoProceso.PosicionProceso estado = EstadoProceso.PosicionProceso.Desconocido;

			if (proceso == ProcesoEnCPU)
				estado = EstadoProceso.PosicionProceso.CPU;
			else if (proceso == ProcesoEnEntradaSalida)
				estado = EstadoProceso.PosicionProceso.EntradaSalida;
			else if (EstaEnPreparados(proceso))
				estado = EstadoProceso.PosicionProceso.ColaPreparados;
			else if (EstaEnColaES(proceso))
				estado = EstadoProceso.PosicionProceso.ColaES;

			return estado;
		}

		/// <summary>
		/// Consulta si un proceso se encuentra en alguna de las colas de procesos
		/// preparados para ejecución.
		/// </summary>
		/// <param name="proceso">Proceso por el que se pregunta.</param>
		/// <returns>true si el proceso se encuentra preparado para ejecución. false en caso contrario.</returns>
		private bool EstaEnPreparados(IProceso proceso)
		{
			bool encontrado = false;

			int c = 0;
			while (c < colasPreparados.Count && !encontrado)
			{
				if (colasPreparados[c].Contiene(proceso))
					encontrado = true;
				else
					++c;
			}

			return encontrado;
		}

		/// <summary>
		/// Consulta si un proceso se encuentra en alguna de las colas de procesos
		/// esperando entrada/salida.
		/// </summary>
		/// <param name="proceso">Proceso por el que se pregunta.</param>
		/// <returns>true si el proceso se encuentra esperando para entrada/salida. false en caso contrario.</returns>
		private bool EstaEnColaES(IProceso proceso)
		{
			bool encontrado = false;

			int c = 0;
			while (c < colasEntradaSalida.Count && !encontrado)
			{
				if (colasEntradaSalida[c].Contiene(proceso))
					encontrado = true;
				else
					++c;
			}

			return encontrado;
		}

		/// <summary>
		/// Ejecuta un ciclo de simulación. Implementa la lógica de planificación.
		/// </summary>
        public void Ciclo()
        {
			// PENDIENTE: establecer la prioridad para entrar en cola de preparados.

			// Lanza el evento de comienzo de ciclo. En este punto es donde se deben insertar los nuevos procesos.
			OnComienzaCiclo(cicloActual);

			//////////////////////////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////////////////////////

			if (dispositivoES != null)
			{
				if (dispositivoES.EntradaSalidaFinalizada())
				{
					// PENDIENTE: en MLFQ habría que ver si hay que asociar al proceso a una cola distinta
					// (dispositivoES.cola = nuevaColaPreparados).
					EncolarEnPreparados(dispositivoES);
					AsignarES(null);
				}
			}

			if (CPU != null)
			{
				// Si el proceso ha terminado (tanto CPU como ES) lo saca de la CPU.
				if (CPU.ProcesoFinalizado())
				{
					AsignarCPU(null);
				}
				// Si ha pedido ES lo encola en la cola de ES y lo saca de la CPU.
				else if (!CPU.EntradaSalidaFinalizada())
				{
					colasEntradaSalida[0].Encolar(ProcesoEnCPU);
					AsignarCPU(null);
				}
				// Si según la estrategia de la cola donde se encontraba el proceso le toca
				// abandonar la CPU (por ejemplo, en RR expira su quantum) lo saca de la CPU
				// y lo vuelve a encolar.
				else if (CPU.Cola.DebeSalirProceso(this))
				{
                    // PENDIENTE: en MLFQ habría que ver si hay que asociar al proceso a una cola distinta
                    // (dispositivoES.cola = nuevaColaPreparados).
					EncolarEnPreparados(ProcesoEnCPU);
					AsignarCPU(null);
				}

				// En cualquier otro caso el proceso sigue asignado a la CPU.
			}

			//////////////////////////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////////////////////////

			// Si la CPU está libre la ocupa con el siguiente proceso en cola de preparados.
			if (CPU == null)
			{
				IProceso p = DesencolarDePreparados();
				if (p != null)
					AsignarCPU(p);
			}

			// Si ES está libre se ocupa con el siguiente proceso en cola de ES.
			if (dispositivoES == null)
			{
				if (colasEntradaSalida[0].Elementos() > 0)
					AsignarES(colasEntradaSalida[0].Desencolar());
			}

			//////////////////////////////////////////////////////////////////////////////////////////////////////
			// Avanza los contadores que llevan la cuenta de cuantos ciclos de CPU y de ES llevan los procesos 
			// actuales.
			//////////////////////////////////////////////////////////////////////////////////////////////////////
			if (dispositivoES != null)
			{
				dispositivoES.Ciclo(cicloActual);
				++ciclosEnES;
			}

			if (CPU != null)
			{
				CPU.Ciclo(cicloActual);
				++ciclosEnCPU;
			}

			//////////////////////////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////////////////////////

			// Lanza el evento de finalización de ciclo.
			OnFinalizaCiclo(cicloActual);

			// Si CPU y ES están vacias entonces finaliza la simulación.
            if (CPU == null && ProcesoEnEntradaSalida==null)
                DetenerSimulacion();
			// Si no, avanza un ciclo.
            else
                ++cicloActual;
        }

		/// <summary>
		/// Asigna un proceso a la CPU.
		/// </summary>
		/// <param name="proceso">Proceso que pasa a ocupar la CPU.</param>
		private void AsignarCPU(IProceso proceso)
		{
			CPU = proceso;
			ciclosEnCPU = 0;
		}

		/// <summary>
		/// Asigna un proceso a E/S.
		/// </summary>
		/// <param name="proceso">Proceso que pasa a ES.</param>
		private void AsignarES(IProceso proceso)
		{
			dispositivoES = proceso;
			ciclosEnES = 0;
		}

		/// <summary>
		/// Lanza el evento de comienzo de simulación.
		/// </summary>
		public virtual void OnComienzaSimulacion()
		{
			if (ComienzaSimulacion != null)
				ComienzaSimulacion(this, null);
		}

		/// <summary>
		/// Lanza el evento de finalización de simulación.
		/// </summary>
		public virtual void OnFinalizaSimulacion()
		{
			if (FinalizaSimulacion != null)
				FinalizaSimulacion(this, null);
		}

		/// <summary>
		/// Lanza el evento de comienzo de ciclo.
		/// </summary>
		/// <param name="ciclo">Ciclo de simulación de comienza.</param>
		public virtual void OnComienzaCiclo(int ciclo)
		{
			if (ComienzaCiclo != null)
			{
				CicloEventArgs args = new CicloEventArgs(ciclo);
				ComienzaCiclo(this, args);
			}
		}

		/// <summary>
		/// Lanza el evento de finalización de ciclo.
		/// </summary>
		/// <param name="ciclo">Ciclo de simulación de finaliza.</param>
		public virtual void OnFinalizaCiclo(int ciclo)
		{
			if (FinalizaCiclo != null)
			{
				CicloEventArgs args = new CicloEventArgs(ciclo);
				FinalizaCiclo(this, args);
			}
		}
    }
}
